#include <glib.h>
#include <enet/enet.h>
#include <stdio.h>

#include "logging.h"
#include "netdata.h"
#include "mplayer-control.h"

ENetHost * client;
ENetPeer * servpeer;
gboolean leader = FALSE;

gchar * filename = NULL;
gchar * server;
gint port;
gdouble accuracy;
gint laziness;
gchar * mplayer_opts;

static GOptionEntry entries[] = 
{
  { "server", 's', 0, G_OPTION_ARG_STRING, &server, "Hostname/IP of the server", "HOST" },
  { "port", 'p', 0, G_OPTION_ARG_INT, &port, "UDP port on the server to connect to", "PORT" },
  { "file", 'f', 0, G_OPTION_ARG_FILENAME, &filename, "Filename for video to play", "FILENAME" },
  { "accuracy", 'a', 0, G_OPTION_ARG_DOUBLE, &accuracy, "Sync to this accuracy", "SECS" },
  { "mplayer-opts", 'm', 0, G_OPTION_ARG_STRING, &mplayer_opts, "Change mplayer options, overrides config", "OPTS" },
  { NULL }
};


void client_time(GString* pos) {
    if (leader) {
        g_string_prepend_c(pos, SYNC);
        ENetPacket * packet = enet_packet_create(pos->str, pos->len, 0);
        g_string_free(pos, TRUE);
        enet_peer_send(servpeer, 0, packet);
    }
}

void client_pause(gboolean status) {
    if (leader) {
        char data[] = { PAUSE, status};
        ENetPacket * packet = enet_packet_create(&data, 2, 
            ENET_PACKET_FLAG_RELIABLE);
        enet_peer_send(servpeer, 0, packet);
        mplayerc_pause(status);
    }
}

gboolean client_handler() {
    ENetEvent event;
    while (enet_host_service (client, & event, CLIENT_WAIT) > 0) {
        switch (event.type) {
        case ENET_EVENT_TYPE_RECEIVE:
            g_debug("Got instruction:%d", event.packet->data[0]);
            switch(event.packet->data[0]) {
            case PING:
                g_debug("PONG! (not implemented)\n");
                break;
            case PAUSE:
                if (!leader) {
                    g_debug("PAUSE from network as non-leader");
                    mplayerc_pause((gboolean)event.packet->data[1]);
                } else {
                    g_debug("PAUSE from network as leader, THIS SHOULD NEVER HAPPEN");
                }
                break;
            case SYNC:
                g_debug("SYNC from network");
                mplayerc_sync_timing(g_strtod((char *)(event.packet->data+1), NULL), accuracy);
                break;
            case FORCE_SYNC:
                g_debug("FORCE_SYNC from network");
                mplayerc_ask_all();
                break;
            case LEADER:
                g_message("You got leader!");
                leader = TRUE;
                mplayerc_start_auto(&laziness);
                break;
            case FIGHT:
                enet_peer_send(servpeer, 0, event.packet);
                g_debug("Fighting for leader...");
            default:
                break;
            }
            break;
           
        case ENET_EVENT_TYPE_DISCONNECT:
            g_critical ("Disconected :(");
            return FALSE;
        default:
            break;
        }
    }
    return TRUE;
}

int main (int argc, char ** argv) {
    GError *error = NULL;
    GOptionContext *context;
    GKeyFile* kf;
    gchar *mpcmd;
    gboolean running = TRUE;
    kf = g_key_file_new();
    g_key_file_load_from_file(kf, "mplayer-share.ini", 0, NULL);
    
    accuracy = g_key_file_get_double(kf, "client", "accuracy", NULL);
    if (accuracy == 0)
        accuracy = CLIENT_ACCURACY;
    laziness = g_key_file_get_integer(kf, "client", "laziness", NULL);
    if (laziness == 0)
        laziness = CLIENT_LAZINESS;
    port = g_key_file_get_integer(kf, "client", "defaultPort", NULL);
    if (port == 0)
        port = PORT;
    server = g_key_file_get_string(kf, "client", "defaultServer", NULL);
    mpcmd = g_key_file_get_string(kf, "mplayer", "mpcmd", NULL);
    if (mpcmd == NULL)
        mpcmd = "mplayer";
    mplayer_opts = g_key_file_get_string(kf, "mplayer", "mpopts", NULL);
    g_key_file_free(kf);
    
    context = g_option_context_new ("- mplayer-share v1 client");
    g_option_context_add_main_entries (context, entries, NULL);
    if (!g_option_context_parse (context, &argc, &argv, &error)) {
        g_critical ("option parsing failed: %s\n", error->message);
        return EXIT_FAILURE;
    }
    
    if (mplayer_opts == NULL)
        mplayer_opts = "";
    
    if (server == NULL) {
        g_critical("No server specified. Read --help.");
        return EXIT_FAILURE;
    }
    
    if (filename == NULL) {
        g_critical("No filename specified. Read --help.");
        return EXIT_FAILURE;
    }


    g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG, log_debug, NULL);
    if (enet_initialize () != 0) {
        g_critical ("An error occurred while initializing ENet.\n");
        return EXIT_FAILURE;
    }
    atexit (enet_deinitialize);

    client = enet_host_create (NULL, 32, 2, 0, 0);
    if (client == NULL) {
        g_critical ("An error occurred while trying to create an ENet client host.\n");
        exit (EXIT_FAILURE);
    }
    ENetAddress address;
    ENetEvent event;

    /* Connect to server */
    enet_address_set_host (&address, server);
    address.port = port;
    g_message("Attempting connection to server %s:%d...", server, port);
    /* Initiate the connection, allocating the two channels 0 and 1. */
    servpeer = enet_host_connect (client, & address, 2, 0);    
    
    if (servpeer == NULL) {
       g_critical ("No available peers for initiating an ENet connection.\n");
       return EXIT_FAILURE;
    }
    
    if (enet_host_service (client, & event, CLIENT_CONNECT_TIME) > 0 &&
        event.type == ENET_EVENT_TYPE_CONNECT) {
        g_message ("Connection succeeded.");
    } else {
        enet_peer_reset (servpeer);
        g_critical ("Connection failed. Exiting.");
        return EXIT_FAILURE;
    }
    
    g_thread_init(NULL);
    
    if (!mplayerc_start(mpcmd, mplayer_opts, filename, &client_time, &client_pause, &running)) {
        g_critical("Could not launch mplayer! Exiting...");
        return EXIT_FAILURE;
    }

    while (client_handler() && running);
    g_debug("Exiting cleanly...");

    mplayerc_quit();
    
    enet_peer_disconnect (servpeer, 0);

    while (enet_host_service (client, & event, CLIENT_CONNECT_TIME) > 0)  {
        switch (event.type) {
        case ENET_EVENT_TYPE_RECEIVE:
            enet_packet_destroy (event.packet);
            break;
        case ENET_EVENT_TYPE_DISCONNECT:
            g_debug("Disconnect success");
            return EXIT_SUCCESS;
        default:
            break;
        }
    }
    
    enet_peer_reset (servpeer);
    return EXIT_SUCCESS;
}
